package com.yeziji.job.business.jobInfo.service.impl;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryChain;
import com.yeziji.common.IServiceImpl;
import com.yeziji.common.context.OnlineContext;
import com.yeziji.job.business.jobInfo.base.JobInfo;
import com.yeziji.job.business.jobInfo.dto.params.JobInfoQueryDTO;
import com.yeziji.job.business.jobInfo.entity.JobInfoEntity;
import com.yeziji.job.business.jobInfo.mapper.JobInfoMapper;
import com.yeziji.job.business.jobInfo.service.JobInfoService;
import com.yeziji.job.common.JobContext;
import com.yeziji.job.common.JobHolder;
import com.yeziji.job.common.base.JobContextInfo;
import com.yeziji.job.common.exception.JobException;
import com.yeziji.job.common.log.YzjJobLogger;
import com.yeziji.job.common.msg.JobErrorMsg;
import com.yeziji.job.constant.JobConstants;
import com.yeziji.job.constant.JobMisfirePolicyEnum;
import com.yeziji.job.constant.JobStatusEnum;
import com.yeziji.job.utils.JobUtils;
import com.yeziji.utils.ServletUtils;
import com.yeziji.utils.expansion.Asserts;
import com.yeziji.utils.expansion.Str2;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;

/**
 * 定时任务信息 服务层实现。
 *
 * @author system
 * @since 2024-05-08
 */
@Slf4j
@Service
public class JobInfoServiceImpl extends IServiceImpl<JobInfoMapper, JobInfoEntity> implements JobInfoService {
    @Resource
    private Scheduler scheduler;
    @Resource
    private HttpServletRequest httpServletRequest;

    private static QueryChain<JobInfoEntity> getJobInfoMapperQueryChain(JobInfoQueryDTO queryDTO) {
        final String name = Str2.nullEmpty(queryDTO.getName());
        final String groupName = Str2.nullEmpty(queryDTO.getGroupName());
        final String invokeTarget = Str2.nullEmpty(queryDTO.getInvokeTarget());
        final String responsiblePerson = Str2.nullEmpty(queryDTO.getResponsiblePerson());
        final Integer status = queryDTO.getStatus();
        return QueryChain.of(JobInfoEntity.class)
                .where(JobInfoEntity::getName).likeLeft(name, StrUtil.isNotBlank(name))
                .and(JobInfoEntity::getStatus).eq(status, status != null)
                .and(JobInfoEntity::getGroupName).likeLeft(groupName, StrUtil.isNotBlank(groupName))
                .and(JobInfoEntity::getInvokeTarget).likeLeft(invokeTarget, StrUtil.isNotBlank(invokeTarget))
                .and(JobInfoEntity::getResponsiblePerson).likeLeft(responsiblePerson, StrUtil.isNotBlank(responsiblePerson));
    }

    @PostConstruct
    public void init() throws SchedulerException {
        this.scheduler.clear();
        List<JobInfoEntity> list = this.list();
        for (JobInfoEntity jobInfoEntity : list) {
            JobUtils.register(this.scheduler, JobInfo.convertByEntity(jobInfoEntity));
        }
    }

    @Override
    public boolean create(JobInfo jobInfo) {
        this.checkAndFill(jobInfo);

        // 創建任務不一定需要存在
        if (this.saveOrUpdate(jobInfo, false)) {
            // register job
            JobUtils.register(this.scheduler, jobInfo);
        }
        return true;
    }

    @Override
    public boolean update(JobInfo jobInfo) {
        this.checkAndFill(jobInfo);

        // 必须要存在任务才能够更新
        if (this.saveOrUpdate(jobInfo, true)) {
            // register 会去判断是否存在，如果存在会进行删除然后替换
            JobUtils.register(this.scheduler, jobInfo);
        }
        return true;
    }

    @Override
    public boolean run(JobInfo jobInfo) {
        JobInfoEntity jobInfoEntity = Asserts.notNull(this.getById(jobInfo.getId()), JobErrorMsg.JOB_NOT_REGISTER);
        // copy base attrs by entity
        jobInfo.copyBaseAttrsByEntity(jobInfoEntity);
        JobKey jobKey = JobUtils.getJobKey(jobInfo);
        if (JobUtils.exists(this.scheduler, jobKey)) {
            String flag;
            try {
                 JobContextInfo jobContextInfo = JobContextInfo.builder()
                        .jobInfo(jobInfo)
                        .triggerIp(OnlineContext.isHasSession() ? OnlineContext.getIp() : ServletUtils.getIpAddress(httpServletRequest))
                        .build();
                flag = JobContext.bind(jobContextInfo);
                YzjJobLogger.info(">>> run flag: {}", flag);
                // 指定执行参数
                JobDataMap dataMap = new JobDataMap();
                dataMap.put(JobConstants.JOB_PARAMS, jobContextInfo);
                // 触发任务
                this.scheduler.triggerJob(jobKey, dataMap);
            } catch (SchedulerException e) {
                log.error("[{}]: 执行任务异常", jobKey.getName(), e);
                throw new JobException(JobErrorMsg.JOB_EXEC_ERROR);
            }
        }
        return true;
    }

    @Override
    public boolean paused(Long id) {
        // 更新任务状态
        JobInfoEntity jobInfoEntity = this.getById(id, JobErrorMsg.JOB_NOT_REGISTER);
        jobInfoEntity.setStatus(JobStatusEnum.PAUSED.getCode());

        if (this.updateById(jobInfoEntity)) {
            JobKey jobKey = JobUtils.getJobKey(JobInfo.convertByEntity(jobInfoEntity));
            if (JobUtils.exists(this.scheduler, jobKey)) {
                try {
                    this.scheduler.pauseJob(jobKey);
                } catch (Exception e) {
                    log.error("[{}]: 暂停任务异常", jobKey.getName(), e);
                    throw new JobException(JobErrorMsg.JOB_PAUSED_ERROR);
                }
            }
        }
        return true;
    }

    @Override
    public boolean resume(Long id) {
        JobInfoEntity jobInfoEntity = this.getById(id, JobErrorMsg.JOB_NOT_REGISTER);
        jobInfoEntity.setStatus(JobStatusEnum.START.getCode());

        if (this.updateById(jobInfoEntity)) {
            JobKey jobKey = JobUtils.getJobKey(JobInfo.convertByEntity(jobInfoEntity));
            if (JobUtils.exists(this.scheduler, jobKey)) {
                try {
                    this.scheduler.resumeJob(jobKey);
                } catch (Exception e) {
                    log.error("[{}]: 恢复任务异常", jobKey.getName(), e);
                    throw new JobException(JobErrorMsg.JOB_RESUME_ERROR);
                }
            }
        }
        return true;
    }

    @Override
    public boolean delete(Long id) {
        JobInfoEntity jobInfoEntity = this.getById(id, JobErrorMsg.JOB_NOT_REGISTER);

        JobKey jobKey = JobUtils.getJobKey(JobInfo.convertByEntity(jobInfoEntity));
        if (JobUtils.exists(this.scheduler, jobKey)) {
            try {
                this.scheduler.deleteJob(jobKey);
            } catch (Exception e) {
                log.error("[{}]: 删除任务异常", jobInfoEntity.getName(), e);
                throw new JobException(JobErrorMsg.JOB_DELETE_ERROR);
            }
        }
        return true;
    }

    @Override
    public boolean saveOrUpdate(JobInfo jobInfo, boolean requiredExists) {
        Long id = jobInfo.getId();
        String name = jobInfo.getName();
        Optional<JobInfoEntity> jobInfoOpt;
        if (id == null) {
            jobInfoOpt = this.queryChain()
                    .where(JobInfoEntity::getGroupName).eq(jobInfo.getGroupName())
                    .and(JobInfoEntity::getName).eq(name)
                    .oneOpt();
        } else {
            jobInfoOpt = Optional.ofNullable(this.getById(id));
        }
        // save or update db
        if (jobInfoOpt.isPresent()) {
            JobInfoEntity jobInfoEntity = jobInfoOpt.get();
            jobInfo.copyToEntity(jobInfoEntity);
            this.updateById(jobInfoEntity);
        } else {
            if (requiredExists) {
                throw new JobException(JobErrorMsg.JOB_NOT_REGISTER);
            }
            this.save(jobInfo.convertToEntity());
        }
        return true;
    }

    @Override
    public Page<JobInfo> pageAsJobInfo(JobInfoQueryDTO queryDTO) {
        return getJobInfoMapperQueryChain(queryDTO).pageAs(queryDTO.getOrDefaultPage(), queryDTO.getQueryClass());
    }

    @Override
    public List<JobInfo> listAsJobInfo(JobInfoQueryDTO queryDTO) {
        return getJobInfoMapperQueryChain(queryDTO).listAs(queryDTO.getQueryClass());
    }

    /**
     * 检查并填充 jobInfo 信息
     *
     * @param jobInfo 任务信息
     */
    private void checkAndFill(JobInfo jobInfo) {
        // assert check: required live in holder
        Asserts.notBlank(jobInfo.getName(), JobErrorMsg.JOB_NAME_IS_NOT_BLANK);
        Asserts.notBlank(jobInfo.getCron(), JobErrorMsg.JOB_CRON_IS_NOT_BLANK);
        Asserts.isFalse(JobHolder.contain(jobInfo.getInvokeTarget()), JobErrorMsg.JOB_NOT_REGISTER);
        if (!CronExpression.isValidExpression(jobInfo.getCron())) {
            Asserts.error(JobErrorMsg.JOB_CRON_IS_FORMAT_ERROR);
        }

        // fill default group name
        String groupName = jobInfo.getGroupName();
        if (StrUtil.isBlank(groupName)) {
            groupName = JobConstants.DEFAULT_GROUP_NAME;
            jobInfo.setGroupName(groupName);
        }
        Integer misfirePolicy = jobInfo.getMisfirePolicy();
        if (misfirePolicy == null) {
            jobInfo.setMisfirePolicy(JobMisfirePolicyEnum.DEFAULT.getCode());
        }
        Boolean isConcurrent = jobInfo.getIsConcurrent();
        if (isConcurrent == null) {
            jobInfo.setIsConcurrent(false);
        }
    }
}
